//  On my honor:
//	
//  - I have not used source code obtained from another student, 
//    or any other unauthorized source, either modified or unmodified.  
//	
//  - All source code and documentation used in my program 
//    is either my original work, or was derived by me from the source
//    code published in the textbook for this course.
//	
//  - I have not discussed coding details about this project with anyone
//    other than my instructor, ACM/UPE tutors or the GTAs assigned to this
//    course. I understand that I may discuss the concepts of this program
//    with other students, and that another student may help me debug my
//    program so long as neither of us writes anything during the discussion
//    or modifies any computer file during the discussion.  I have violated
//    neither the spirit nor letter of this restriction.
//	

/**
 * @author Christopher Suh, Eric Yoo
 * 
 */
public class MemManager {

	private byte[] pool;
	private LinkedList freeBlockList;

	/**
	 * Constructor
	 * 
	 * @param poolsize
	 *            size of the byte array to allocate
	 */
	MemManager(int poolsize) {
		pool = new byte[poolsize];
		freeBlockList = new LinkedList();
		freeBlockList.insert(0, poolsize);
	}

	/**
	 * Insert a record and return its position handle.
	 * 
	 * @param space
	 *            record to be inserted
	 * @param size
	 *            size of record in bytes
	 * @return
	 */
	public Handle insert(byte[] space, int size) {
		// use String.getBytes() method to change String object to bytes
		// make sure coordinates are stored as two's-comp binary
		// values in 4 bytes (how?)
		// - Check out ByteBuffer.putInt(num).array();
		int index = 0;
		size++;
		Handle newHandle = null;

		// check size for valid number,

		// find memory block in pool for space using best fit rule
		freeBlockList.resetCurNode();
		ListNode cur = freeBlockList.getNext(), bestFit = null;
		while (cur != null) {
			if (cur.getSize() >= size) {
				bestFit = freeBlockList.removeNode(cur);
				index = bestFit.getIndex();
				break;
			}
			cur = freeBlockList.getNext();
		}

		// if a memory block large enough to fit the new data is found,
		// create a new handle to represent the newly allocated data,
		// otherwise, return null.
		if (bestFit != null) {
			newHandle = new Handle(index);

			// if the best fit free block is larger than needed, insert a
			// free block into the list of according index and size.
			if (bestFit.getSize() > size) {
				freeBlockList.insert(index + size, bestFit.getSize() - size);
			}

			// Since there is a free block large enough to fit the data, insert
			// the given data into the free block.
			pool[index] = (new java.lang.Integer(size - 1)).byteValue();
			for (int i = 1; i < size; i++) {
				pool[index + i] = space[i - 1];
			}
		}

		// Return the newly created handle corresponding to the inserted data.
		// If there is not enough room to hold the new data, a null value will
		// be returned.
		return newHandle;
	}

	/**
	 * Free a block at posHandle. Merge adjacent blocks if appropriate.
	 * 
	 * @param theHandle
	 *            reference to record within memory pool
	 */
	public void remove(Handle theHandle) {
		int index = theHandle.getID();
		int size = pool[index] + 1;

		// Traverse through the freelist and search for adjacent blocks.
		freeBlockList.resetCurNode();
		ListNode cur = freeBlockList.getNext();
		ListNode before = null, after = null;
		while (cur != null) {
			if (cur.getIndex() + cur.getSize() == index) {
				before = cur;
			}
			if (index + size == cur.getIndex()) {
				after = cur;
			}

			cur = freeBlockList.getNext();
		}

		// If there is are any free blocks adjacent to the newly created free
		// block, merge them and insert the new free block.
		if (before != null) {
			before = freeBlockList.removeNode(before);
			index = before.getIndex();
			size = size + before.getSize();
		}
		if (after != null) {
			after = freeBlockList.removeNode(after);
			size = size + after.getSize();
		}
		freeBlockList.insert(index, size);
	}

	/**
	 * Return the record with handle posHandle, up to size bytes. Place the
	 * record into space.
	 * 
	 * @param space
	 *            target byte array to hold record
	 * @param hndl
	 *            reference to record within memory pool
	 * @param size
	 *            number of bytes of target record
	 * @return the number of bytes copied into space
	 */
	public int get(byte[] space, Handle hndl, int size)
			throws ArrayIndexOutOfBoundsException {

		int index = hndl.getID();
		if (pool[index] > 255) {
			throw new ArrayIndexOutOfBoundsException();
		}

		// copy byte array
		for (int i = 1; i <= pool[index] && i <= size; i++) {
			space[i - 1] = pool[index + i];
		}
		/*
		 * for(int i = index + 1; i < index + pool[index] + 1; i++) { space[i -
		 * index - 1] = pool[index]; }
		 */

		return pool[index];
	}

	/**
	 * Dump a printout of the freeblock list in order of increasing size of
	 * memory block.
	 */
	public void dump() {
		freeBlockList.resetCurNode();
		ListNode cur = freeBlockList.getNext();

		// If freelist is not empty, print out the free blocks.
		if (cur != null) {
			System.out.println("Freelist: [" + cur.getIndex() + ", "
					+ (cur.getIndex() + cur.getSize() - 1) + "] ("
					+ cur.getSize() + " bytes);");

			// Traverse through the freelist and print out the free blocks.
			while (true) {
				cur = freeBlockList.getNext();
				if (cur != null) {
					System.out.println("          [" + cur.getIndex() + ", "
							+ (cur.getIndex() + cur.getSize() - 1) + "] ("
							+ cur.getSize() + " bytes);");
				} else {
					break;
				}
			}
		}
		else
		{
			System.out.println("FreeBlockList is empty.");
		}
		System.out.println();
	}
}
